using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace WB.Mvc.Forms
{
	/// <summary>
	/// <see cref="FieldGroup"/> with fields auto generated from a model.
	/// </summary>
	public class ModelFieldGroupStrategy : IFieldGroupStrategy
	{
		private IList<IFieldStrategy> fieldStrategies;
		private IList<IFieldGroupStrategy> groupStrategies;

		/// <summary>
		/// Creates an instance of <see cref="ModelFieldGroupStrategy"/>.
		/// </summary>
		public ModelFieldGroupStrategy()
			: this(new IFieldStrategy[] { })
		{
		}

		/// <summary>
		/// Creates an instance of <see cref="ModelFieldGroupStrategy"/>.
		/// </summary>
		/// <param name="strategies">User strategies for creating fields.</param>
		public ModelFieldGroupStrategy(params IFieldStrategy[] strategies)
		{
			fieldStrategies = strategies.ToList();
			RegisterDefaultStrategies();
		}

		/// <summary>
		/// Get or set the strategies to be used for 
		/// additional fieldgruops.
		/// </summary>
		public virtual IList<IFieldGroupStrategy> GroupStrategies
		{
			get { return groupStrategies; }
			set { groupStrategies = value; }
		}

		public IDictionary<FieldGroup, object> Create(object model)
		{
			return Create(model, string.Empty, descriptor => true);
		}

		public IDictionary<FieldGroup, object> Create(object model, Predicate<PropertyDescriptor> shouldInclude)
		{
			return Create(model, string.Empty, shouldInclude);
		}

		public IDictionary<FieldGroup, object> Create(object model, string prefix, Predicate<PropertyDescriptor> shouldInclude)
		{
			if (model == null) throw new ArgumentNullException("model");

			var groups = new Dictionary<FieldGroup, object>();

			var mainGroup = new ModelFieldGroup();
			groups.Add(mainGroup, model);

			var properties = TypeDescriptor.GetProperties(model);
			foreach (PropertyDescriptor property in properties)
			{
				if (!shouldInclude(property))
					continue;

				Type propertyType = property.PropertyType;
				Field field = ApplyFieldStrategies(property);
				if (field != null)
				{
					field.Name = field.Name.Insert(0, prefix);
					var val = Convert.ToString(property.GetValue(model));
					if (!String.IsNullOrEmpty(val))
						field.Value = val;
					mainGroup.AddField(field, property);
					continue;
				}

				if (propertyType.IsArray)
				{
					// TODO: Implement this.
				}
				else if (propertyType.IsClass)
				{
					var value = property.GetValue(model);
					if (value == null)
					{
						try
						{
							value = Activator.CreateInstance(property.PropertyType);
							property.SetValue(model, value);
						}
						catch (Exception ex)
						{
							throw new InvalidOperationException(string.Format("{0} was null.  Tried to create it but found no default constructor.", property.Name), ex);
						}
					}

					IDictionary<FieldGroup, object> subGroups = Create(value, prefix + property.Name + "_", shouldInclude);
					foreach (var group in subGroups)
					{
						groups.Add(group.Key, group.Value);
					}
				}
			}

			return groups;
		}

		private Field ApplyFieldStrategies(PropertyDescriptor descriptor)
		{
			foreach (IFieldStrategy strategy in fieldStrategies)
			{
				IModelFieldStrategy modelStrategy = strategy as IModelFieldStrategy;
				Field field;
				if (modelStrategy != null)
					field = modelStrategy.Create(descriptor);
				else
					field = strategy.Create(descriptor.Name);
				if (field != null)
				{
					return field;
				}
			}

			return default(Field);
		}

		private void RegisterDefaultStrategies()
		{
			fieldStrategies.Add(new TextAreaConventionFieldStrategy());
			fieldStrategies.Add(new EnumToRadioButtonListFieldStrategy());
			fieldStrategies.Add(new StringToCharFieldStrategy());
			fieldStrategies.Add(new BoolToBooleanFieldStrategy());
			fieldStrategies.Add(new IntToIntegerFieldStrategy());
		}
	}
}